package javango.contrib.jquery.widgets;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.Security;
import java.security.spec.KeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javango.api.Settings;
import javango.contrib.jquery.JqueryWidget;
import javango.contrib.jquery.fields.JqueryLookupField;
import javango.contrib.jquery.widgets.JqueryLookupWidget.PBEEncryptDataString.EncryptionException;
import javango.forms.fields.BoundField;
import javango.forms.widgets.TextInputWidget;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.apache.commons.logging.LogFactory;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import com.google.inject.Inject;

public class JqueryLookupWidget extends TextInputWidget implements JqueryWidget {
	
	public static class PBEEncryptDataString {
		static {
			Security.addProvider(new com.sun.crypto.provider.SunJCE());
		}

	    static public class EncryptionException extends Exception
	    {
	        private EncryptionException(String text, Exception chain)
	        {
	            super(text, chain);
	        }
	    }
	    
	    //private static final String ALGORITHM = "PBEWithMD5AndDes";
	    private static final String ALGORITHM = "PBEWithSHA1AndDESede";
	    //private static final String ALGORITHM = "PBEWithMD5AndTripleDES";
	    
	    public PBEEncryptDataString(String passphrase, byte[] salt, int iterationCount, String characterEncoding) throws EncryptionException
	    {
	        assert(passphrase != null);
	        assert(passphrase.length() >= 6);
	        assert(salt != null);
	        assert(salt.length == 8);
	        assert((iterationCount > 6) && (iterationCount < 20));
	        assert(characterEncoding != null);
	        
	        try
	        {
	            PBEParameterSpec params = new PBEParameterSpec(salt, iterationCount);
	            
	            KeySpec keySpec = new PBEKeySpec(passphrase.toCharArray());
	            SecretKey key = SecretKeyFactory.getInstance(ALGORITHM, "SunJCE").generateSecret(keySpec);
	            
	            this.characterEncoding = characterEncoding;
	            this.encryptCipher = Cipher.getInstance(ALGORITHM, "SunJCE");
	            this.encryptCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, params);
	            
	            this.decryptCipher = Cipher.getInstance(ALGORITHM, "SunJCE");
	            this.decryptCipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, params);
	        }
	        catch (Exception e)
	        {
	            throw new EncryptionException("Problem constucting " + this.getClass().getName(), e);
	        }
	    }
	    
	    synchronized public String encrypt(String dataString) throws EncryptionException
	    {
	        assert dataString != null;
	        
	        try
	        {
	            byte[] dataStringBytes = dataString.getBytes(characterEncoding);
	            byte[] encryptedDataStringBytes = this.encryptCipher.doFinal(dataStringBytes);
	            String encodedEncryptedDataString = this.base64Encoder.encode(encryptedDataStringBytes);
	            return encodedEncryptedDataString;
	        }
	        catch (Exception e)
	        {
	            throw new EncryptionException("Problem encrypting string", e);
	        }
	    }
	    
	    synchronized public String decrypt(String encodedEncryptedDataString) throws EncryptionException
	    {
	        assert encodedEncryptedDataString != null;
	        
	        try
	        {
	            byte[] encryptedDataStringBytes = this.base64Decoder.decodeBuffer(encodedEncryptedDataString);
	            byte[] dataStringBytes = this.decryptCipher.doFinal(encryptedDataStringBytes);
	            String recoveredDataString = new String(dataStringBytes, characterEncoding);
	            return recoveredDataString;
	        }
	        catch (Exception e)
	        {
	            throw new EncryptionException("Problem decrypting string", e);
	        }
	    }	
	    
	    private String characterEncoding;
	    private Cipher encryptCipher;
	    private Cipher decryptCipher;
	    private BASE64Encoder base64Encoder = new BASE64Encoder();
	    private BASE64Decoder base64Decoder = new BASE64Decoder();
	}
	
	public static String encrypt(Map<String, String> params) {
		try {
			StringBuilder b = new StringBuilder();
			for(Entry<String, String> e : params.entrySet()) {
				b.append(e.getKey());
				b.append("=");
				b.append(e.getValue());
				b.append(";");
			}
	
			String encryptedString = new PBEEncryptDataString("The Password", salt, 9, "UTF-8").encrypt(b.toString());
			return encryptedString;
		} catch (EncryptionException e) {
			LogFactory.getLog(JqueryLookupWidget.class).error(e,e);
		}
		return null;
	}
		
    static final byte[] salt =  {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

	public static Map<String, String> decrypt(String encryptedURL) throws EncryptionException, UnsupportedEncodingException  {
		// TODO Need the correct encoding settings maybe...
		String encryptedString = encryptedURL;
		// TODO need beter mtehod for passing the encryption provider...  Guice anyone...
		String result = new PBEEncryptDataString("The Password", salt, 9, "UTF-8").decrypt(encryptedString);
		
		Map<String, String> resultMap = new HashMap<String, String>();
		for(String pair : result.split(";")) {
			String[] v = pair.split("=");
			resultMap.put(v[0], v[1]);
		}
		return resultMap;
	}
	
	
	public String getScript(BoundField field) {
		try {
			if (!field.getField().isEditable()) return "";
	
			if (field.getField() instanceof JqueryLookupField) {
				JqueryLookupField lookupField = (JqueryLookupField) field.getField();

				// TODO Need to get the form's id property for the format
				// maybe from the Field's attr map
				String fieldId = String.format("id_%s", field.getHtmlName());
				
				Map<String, String> urlParameters = new HashMap<String, String>();
				urlParameters.put("model", lookupField.getModel().getCanonicalName());
				urlParameters.put("display", lookupField.getDisplay());
				urlParameters.put("search", lookupField.getSearch());
				urlParameters.put("results", lookupField.getResults());
				urlParameters.put("field", fieldId);
				if (lookupField.getOrderBy() != null) urlParameters.put("order_by", lookupField.getOrderBy());
	
				return String.format(
						"$('#%1$s').javangoLookupWidget('%2$s', '%3$s');", 
						fieldId,									// 1 TODO not correct, should use ID
						URLEncoder.encode(encrypt(urlParameters), "UTF-8"),		// 2
						settings.get("jquery_media_url")						// 3
					);
			}
			return "<!-- " + field.getHtmlName() + " not a JqueryLookupField -->";
		} catch (UnsupportedEncodingException e) {
			LogFactory.getLog(JqueryLookupWidget.class).error(e,e);
			return "<!-- " + field.getHtmlName() + e + " -->";
		}
	}

	Settings settings;
	@Inject
	public JqueryLookupWidget(Settings settings) {
		super();
		this.settings = settings;
	}

	@Override
	public String render(String name, Object value, Map<String, Object> attrs) {		
		return String.format("%3$s", 
				name,								// 1 
				settings.get("jquery_media_url"), 	// 2
				super.render(name, value, attrs));	// 3
	}
}
